import urllib2
import datetime
import threading
import wx
import copy
import os
import sys
import yaml
from collections import OrderedDict


LOGGING_LEVEL = OrderedDict(sorted({
    'CRITICAL': 50,
    'ERROR': 40,
    'WARNING': 30,
    'INFO': 20,
    'DEBUG': 10,
    'SDEBUG': 5,
    'NOTSET': 0
}.items(), key=lambda t: t[1], reverse=True))


def getScriptPath():
    return os.path.dirname(os.path.realpath(sys.argv[0]))


def loadConfigFile(configFilePath='config.yaml'):
    configContent = {}
    with open(configFilePath, 'r') as cFile:
        try:
            configContent = yaml.load(cFile)
        except (IOError, yaml.YAMLError) as e:
            configContent['error'] = str(e)
    return configContent


class Job(object):
    def __init__(self,
                 project='',
                 subproject='None',
                 branch='',
                 testType='',
                 artifacts='',
                 buildId='',
                 resultJob='',
                 status='waiting',
                 startTime=datetime.datetime.now(),
                 timeout=2880,
                 force=False):
        self._startTime = startTime
        self._project = project
        self._subproject = subproject
        self._branch = branch
        self._testType = testType
        self._artifacts = artifacts
        self._buildId = buildId
        self._resultJob = resultJob
        self._status = status
        self._timeout = timeout
        self._force = force
        self._retry = 0
        self._id = "[buildId: {} || JOB: {}/{}/{}]".format(self._buildId, self._project, self._branch, self._testType)
        self.setIdr()

    def setIdr(self):
        retryPart = " || Retry: {}]".format(self._retry) if self._retry > 0 else ""
        self._idr = "{}{}]".format(self._id, retryPart)

    def incrementRetries(self):
        self._retry += 1
        self.setIdr()

    def __eq__(self, other):
        if other is None:
            return False
        elif (self._project == other._project and self._branch == other._branch and
                self._testType == other._testType and self._buildId == other._buildId):
            return True
        return False


# ----------------------------------------------------------------------------------------------------------------------
class JobWithResults(object):
    def __init__(self, job, message='', tmsRepo=None, returnCode=None, file=''):
        self._job = job
        self._tmsRepo = tmsRepo
        self._returnCode = returnCode
        self._file = file
        self._message = message
        self._retry = 0
        self._id = job._id
        self.setIdr()

    def setIdr(self):
        retryPart = " || Retry: {}]".format(self._retry) if self._retry > 0 else ""
        self._idr = "{}{}]".format(self._id, retryPart)

    def incrementRetries(self):
        self._retry += 1
        self.setIdr()

    def resetRetries(self):
        self._retry = 0
        self.setIdr()

# ----------------------------------------------------------------------------------------------------------------------
class Queue(object):
    def __init__(self, qName, logger):
        self._list = []
        self._logger = logger
        self._headerLogInfo = "[" + qName.rjust(19) + "]\t"
        self._listLock = threading.Lock()
        self._viewUpdateRequired = False
        self._qName = qName
        self._updateLock = threading.Lock()
        self._frozen = False
        self.readQueueFromFile()

    def setFrozen(self, frozen):
        self._frozen = frozen

    def block(self):
        self._listLock.acquire()

    def release(self):
        self._listLock.release()

    def put(self, element=None, force=False):
        if type(element) == JobWithResults or type(element) == Job:
            if type(element) == Job:
                force = element._force
            jobData = self.getItemData(element)
            self._listLock.acquire()
            index = 0
            if force:
                self._logger.debug(self._headerLogInfo + "{} Adding new item to queue with force".format(jobData))
                for i in range(0, len(self._list)):
                    if self._list[i]._status == "waiting" and not self._list[i]._force:
                        index = i
                        break
                self._list.insert(index, element)
            else:
                self._logger.debug(self._headerLogInfo + "{} Adding new item to queue".format(jobData))
                self._list.append(element)
                index = len(self._list) - 1
            self._logger.debug(self._headerLogInfo + "{} Element added at index: {}".format(jobData, index))
            self.writeQueueToFile()
            self._listLock.release()
            self.updateView(True)
            return index
        else:
            return -1

    def use(self):
        element = None
        if self._list:
            self._listLock.acquire()
            if not self._frozen:
                if hasattr(self._list[0], '_status'):
                    self._list[0]._status = "running"
                    self._list[0]._startTime = datetime.datetime.now()
                element = self._list[0]
                jobData = self.getItemData(element)
                self._logger.debug(self._headerLogInfo + "{} Reading item from queue".format(jobData))
                self.writeQueueToFile()
                self.updateView(True)
            self._listLock.release()
        return element

    def getJobsList(self):
        self._logger.debug(self._headerLogInfo + "Getting jobs from queue".format(self._qName))
        queue = []
        if self._list:
            self._listLock.acquire()
            queue = copy.deepcopy(self._list)
            self._listLock.release()
        return queue

    def updateView(self, viewUupdateRequired):
        self._updateLock.acquire()
        self._viewUpdateRequired = viewUupdateRequired
        self._updateLock.release()

    def updateRequired(self):
        return self._viewUpdateRequired

    def remove(self, job):
        toReturn = -1  # in case no job found returns -1
        if self._list:
            self._listLock.acquire()
            index = self.findId(job)
            if index > -1:
                jobData = self.getItemData(self._list[index])
                self._logger.debug(self._headerLogInfo + "{} Removing item from queue (queue id: {})"
                                   .format(jobData, index))
                del self._list[index]
                self.writeQueueToFile()
                toReturn = 0  # success
            self.updateView(True)
            self._listLock.release()
        return toReturn

    def findId(self, job):
        if self._list:
            for i in range(len(self._list)):
                if type(job) == Job:
                    if job == self._list[i]:
                        return i
                elif type(job) == JobWithResults:
                    if job._job == self._list[i]._job:
                        return i
        return -1

    def setPriority(self, element_index, new_priority):
        if self._list and element_index >= 0:
            jobData = self.getItemData(self._list[element_index])
            self._logger.debug(self._headerLogInfo + "{} Changing queue order. Setting new priority {}"
                               .format(jobData, new_priority))
            self._listLock.acquire()
            element = self._list.pop(element_index)
            self._list.insert(new_priority, element)
            self.writeQueueToFile()
            self._listLock.release()
            self.updateView(True)

    def getItemData(self, item):
        if type(item) == Job:
            return "[buildId = {} || JOB: {}/{}/{}]".format(item._buildId, item._project,
                                                            item._branch, item._testType)
        else:
            return "[buildId = {} || JOB: {}/{}/{}]".format(item._job._buildId, item._job._project,
                                                            item._job._branch, item._job._testType)

    def isToBeLoaded(self, queueItem):
        if self._qName == "RESULTS_QUEUE":
            jobData = self.getItemData(queueItem)
            runningTime = datetime.datetime.now() - queueItem._job._startTime
            if queueItem._job._timeout < runningTime.total_seconds():
                self._logger.info(self._headerLogInfo + "{} Item timeout expired. Removing from queue".format(jobData))
                return False
        return True

    def writeQueueToFile(self):
        file = os.path.join('logs', self._qName + '.log')
        itemsQueued = False
        if self._list:
            self._logger.debug(self._headerLogInfo + "Writing queue to file: logs/{}{}".format(self._qName, '.log'))
            with open(file, 'w') as f:
                for item in self._list:
                    if type(item) == JobWithResults or (type(item) == Job and item._status != "running"):
                        jobData = self.getItemData(item)
                        self._logger.debug(self._headerLogInfo + "{} Writing to file".format(jobData))
                        if itemsQueued:
                            f.write("<---------splitter--------->\n")
                        f.write(yaml.dump(item))
                        itemsQueued = True
        else:
            self._logger.debug(self._headerLogInfo + "No items exist, removing queue ({}) file: logs/{}{}"
                               .format(self._qName, self._qName, '.log'))
        if os.path.exists(file) and not itemsQueued:
            os.remove(file)

    def readQueueFromFile(self):
        self._logger.debug(self._headerLogInfo + "Reading queue from file: logs/{}{}"
                           .format(self._qName, '.log'))
        file = os.path.join('logs', self._qName + '.log')
        if os.path.exists(file):
            with open(file) as f:
                self._listLock.acquire()
                jobs = f.read().split("<---------splitter--------->")
                for item in jobs:
                    try:
                        job = yaml.load(item)
                        jobData = self.getItemData(job)
                        self._logger.debug(self._headerLogInfo + "Adding item to queue: {}".format(jobData))
                        if self.isToBeLoaded(job):
                            self._list.append(job)
                    except Exception:
                        self._logger.exception(self._headerLogInfo + "Error parsing queue file: {}"
                                               .format(self._qName) + ". Incomplete queue record: {}".format(item))
                self._listLock.release()
            self._logger.debug(self._headerLogInfo + "Removing queue file: logs/{}{}"
                               .format(self._qName, '.log'))
            os.remove(file)


# ----------------------------------------------------------------------------------------------------------------------
class MyTextBox(wx.Frame):
    def __init__(self, parent, message, sizeX=1200, sizeY=200, fontSize=30):
        wx.Frame.__init__(self, parent, -1, style=wx.STAY_ON_TOP | wx.SIMPLE_BORDER, size=((sizeX, sizeY)))
        self.Bind(wx.EVT_CLOSE, self.onClose)
        self._fontSize = fontSize

        self.SetBackgroundColour((100, 100, 100))
        self._sizer_v = wx.BoxSizer(wx.VERTICAL)
        self._sizer_h = wx.BoxSizer(wx.HORIZONTAL)

        self._text = wx.StaticText(self)
        self.setMessage(message)
        self._font = wx.Font(self._fontSize, wx.DECORATIVE, wx.ITALIC, wx.BOLD)
        self._text.SetFont(self._font)
        self._text.SetForegroundColour((255, 140, 30))

        self._sizer_h.Add(self._text, 1, wx.CENTER)
        self._sizer_v.Add(self._sizer_h, 1, wx.CENTER)
        self.SetSizer(self._sizer_v)

        self.Center()

    def setMessage(self, message, sizeX=1200, sizeY=200, fsize=30):
        self.Freeze()
        self.SetSize((sizeX, sizeY))
        self.Center()
        self._font = wx.Font(fsize, wx.DECORATIVE, wx.ITALIC, wx.BOLD)
        self._text.SetFont(self._font)
        self._text.SetLabel(message)
        self.Layout()
        self.Thaw()

    def onClose(self, e):
        pass


# ----------------------------------------------------------------------------------------------------------------------
class Downloader():
    def __init__(self):
        self._stopDown = False
        self._thread = None
        self._isDownloading = False
        self._opener_is_set = False

    def setLoginData(self, url, username, password):
        self.installUrlopener(url, username, password)

    def download(self, url, destination):
        self._thread = threading.Thread(target=self.down, args=(url, destination))
        self._thread.start()
        self._isDownloading = True

    def down(self, url, destination):
        toContinue = True
        handler = urllib2.urlopen(url)
        with open(destination, "wb") as fp:
            while not self._stopDown and toContinue:
                data = handler.read(8192)
                fp.write(data)
                toContinue = data
        handler.close()
        self._isDownloading = False

    def installUrlopener(self, url, username, password):
        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        password_mgr.add_password(None, url, username, password)
        handler = urllib2.HTTPBasicAuthHandler(password_mgr)
        opener = urllib2.build_opener(handler)
        opener.open(url)
        urllib2.install_opener(opener)

    def cancel(self):
        self._stopDown = True
        self._isDownloading = False
